package org.red5.server.api;

import java.lang.reflect.Field;

import org.red5.server.Server;
import org.red5.server.api.persistence.IPersistable;
import org.red5.server.api.scope.IBasicScope;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.scope.IScopeHandler;
import org.red5.server.api.scope.IScopeService;
import org.red5.server.scope.Scope;
import org.red5.server.scope.WebScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ScopeUtils {

	private static final Logger log = LoggerFactory.getLogger(ScopeUtils.class);

	private static final int GLOBAL = 0x00;

	private static final int APPLICATION = 0x01;

	private static final int ROOM = 0x02;

	private static final String SERVICE_CACHE_PREFIX = "__service_cache:";

	/**
	 * Constant for slash symbol
	 */
	private static final String SLASH = "/";

	/**
	 * Resolves scope for specified scope and path.
	 *
	 * @param from Scope to use as context (to start from)
	 * @param path Path to resolve
	 * @return	Resolved scope
	 */
	public static IScope resolveScope(IScope from, String path) {
		IScope current = from;
		if (path.startsWith(SLASH)) {
			current = ScopeUtils.findRoot(current);
			path = path.substring(1, path.length());
		}
		if (path.endsWith(SLASH)) {
			path = path.substring(0, path.length() - 1);
		}
		String[] parts = path.split(SLASH);
		for (String part : parts) {
			if (part.equals(".")) {
				continue;
			}
			if (part.equals("..")) {
				if (!current.hasParent()) {
					return null;
				}
				current = current.getParent();
				continue;
			}
			if (!current.hasChildScope(part)) {
				return null;
			}
			current = current.getScope(part);
		}
		return current;
	}

	/**
	 * Finds root scope for specified scope object. Root scope is the top level
	 * scope among scope's parents.
	 *
	 * @param from Scope to find root for
	 * @return	Root scope object
	 */
	public static IScope findRoot(IScope from) {
		IScope current = from;
		while (current.hasParent()) {
			current = current.getParent();
		}
		return current;
	}

	/**
	 * Returns the application scope for specified scope. Application scope has
	 * depth of 1 and has no parent.
	 *
	 * See <code>isApp</code> method for details.
	 *
	 * @param from Scope to find application for
	 * @return		Application scope.
	 */
	public static IScope findApplication(IScope from) {
		IScope current = from;
		while (current.hasParent() && current.getDepth() != APPLICATION) {
			current = current.getParent();
		}
		return current;
	}

	/**
	 * Check whether one scope is an ancestor of another
	 *
	 * @param from Scope
	 * @param ancestor Scope to check
	 * @return <code>true</code> if ancestor scope is really an ancestor of
	 *         scope passed as from parameter, <code>false</code> otherwise.
	 */
	public static boolean isAncestor(IBasicScope from, IBasicScope ancestor) {
		IBasicScope current = from;
		while (current.hasParent()) {
			current = current.getParent();
			if (current.equals(ancestor)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Checks whether scope is root or not
	 *
	 * @param scope Scope to check
	 * @return <code>true</code> if scope is root scope (top level scope),
	 *         <code>false</code> otherwise.
	 */
	public static boolean isRoot(IBasicScope scope) {
		return !scope.hasParent();
	}

	/**
	 * Check whether scope is the global scope (level 0 leaf in scope tree) or
	 * not
	 *
	 * When user connects the following URL: rtmp://localhost/myapp/foo/bar then /
	 * is the global level scope, myapp is app level, foo is room level and bar
	 * is room level as well (but with higher depth level)
	 *
	 * @param scope Scope to check
	 * @return <code>true</code> if scope is the global scope,
	 *         <code>false</code> otherwise.
	 */
	public static boolean isGlobal(IBasicScope scope) {
		return scope.getDepth() == GLOBAL;
	}

	/**
	 * Check whether scope is an application scope (level 1 leaf in scope tree)
	 * or not
	 *
	 * @param scope Scope to check
	 * @return <code>true</code> if scope is an application scope,
	 *         <code>false</code> otherwise.
	 */
	public static boolean isApp(IBasicScope scope) {
		return scope.getDepth() == APPLICATION;
	}

	/**
	 * Check whether scope is a room scope (level 2 leaf in scope tree or lower,
	 * e.g. 3, 4, ...) or not
	 *
	 * @param scope Scope to check
	 * @return <code>true</code> if scope is a room scope, <code>false</code>
	 *         otherwise.
	 */
	public static boolean isRoom(IBasicScope scope) {
		return scope.getDepth() >= ROOM;
	}

	/**
	 * Returns scope service by bean name. See overloaded method for details.
	 * 
	 * @param scope scope
	 * @param name name
	 * @return object
	 */
	public static Object getScopeService(IScope scope, String name) {
		return getScopeService(scope, name, null);
	}

	/**
	 * Returns scope services (e.g. SharedObject, etc) for the scope. Method
	 * uses either bean name passes as a string or class object.
	 *
	 * @param scope
	 *            The scope service belongs to
	 * @param name
	 *            Bean name
	 * @param defaultClass
	 *            Class of service
	 * @return				Service object
	 */
	public static Object getScopeService(IScope scope, String name,
			Class<?> defaultClass) {
		if (scope == null) {
			return null;
		}

		final IContext context = scope.getContext();
		Object result = null;
		
		result = context.getBean(name);
		if(result == null) {
			if (defaultClass == null) {
				return null;
			}
			
			try {
				result = defaultClass.newInstance();
			} catch (Exception e) {
				log.error("{}", e);
				return null;
			}
		} 
		/*ApplicationContext appCtx = context.getApplicationContext();
		Object result;
		if (!appCtx.containsBean(name)) {
			if (defaultClass == null) {
				return null;
			}

			try {
				result = defaultClass.newInstance();
			} catch (Exception e) {
				log.error("{}", e);
				return null;
			}
		} else {
			result = appCtx.getBean(name);
		}*/

		return result;
	}

	/**
	 * Returns scope service that implements a given interface.
	 *
	 * @param scope The scope service belongs to
	 * @param intf The interface the service must implement
	 * @return Service object
	 */
	public static Object getScopeService(IScope scope, Class<?> intf) {
		return getScopeService(scope, intf, null);
	}

	public static Object getScopeService(IScope scope, Class<?> intf, boolean checkHandler) {
		return getScopeService(scope, intf, null, checkHandler);
	}

	/**
	 * Returns scope service that implements a given interface.
	 *
	 * @param scope The scope service belongs to
	 * @param intf The interface the service must implement
	 * @param defaultClass Class that should be used to create a new service if no service was found.
	 * @return Service object
	 */
	public static Object getScopeService(IScope scope, Class<?> intf,
			Class<?> defaultClass) {
		return getScopeService(scope, intf, defaultClass, true);
	}

	public static Object getScopeService(IScope scope, Class<?> intf,
			Class<?> defaultClass, boolean checkHandler) {
		if (scope == null || intf == null) {
			return null;
		}
		// We expect an interface
		assert intf.isInterface();

		String attr = IPersistable.TRANSIENT_PREFIX + SERVICE_CACHE_PREFIX + intf.getCanonicalName();
		if (scope.hasAttribute(attr)) {
			// Return cached service
			return scope.getAttribute(attr);
		}

		Object handler = null;
		if (checkHandler) {
			IScope current = scope;
			while (current != null) {
				IScopeHandler scopeHandler = current.getHandler();
				if (intf.isInstance(scopeHandler)) {
					handler = scopeHandler;
					break;
				}

				if (!current.hasParent()) {
					break;
				}
				current = current.getParent();
			}
		}

		if (handler == null && IScopeService.class.isAssignableFrom(intf)) {
			// We got an IScopeService, try to lookup bean
			Field key = null;
			Object serviceName = null;
			try {
				key = intf.getField("BEAN_NAME");
				serviceName = key.get(null);
				if (!(serviceName instanceof String)) {
					serviceName = null;
				}
			} catch (Exception e) {
				log.debug("No string field 'BEAN_NAME' in that interface");
			}

			if (serviceName != null) {
				handler = getScopeService(scope, (String) serviceName, defaultClass);
			}
		}

		if (handler == null && defaultClass != null) {
			try {
				handler = defaultClass.newInstance();
			} catch (Exception e) {
				log.error("", e);
			}
		}

		// Cache service
		scope.setAttribute(attr, handler);
		return handler;
	}
	
	public static Scope getScope(String app) {
		WebScope webScope = (WebScope)Server.getInstance().getGlobal("default").getScope(app);		
		return webScope;
	}
}
